Skip to content

Conversation

@urnotsam
Copy link
Contributor

@urnotsam urnotsam commented Aug 27, 2025

PR Type

Bug fix, Enhancement


Description

  • Improved ERC-20 transaction and token display logic

  • Fixed null/undefined access for contract info fields

  • Standardized usage of ethers formatUnits for value formatting

  • Enhanced transaction detail view for ERC-20 transfers


Changes walkthrough 📝

Relevant files
Bug fix
useAccountDetailHook.ts
Fix null access and standardize ERC-20 balance formatting

src/frontend/account/AccountDetail/useAccountDetailHook.ts

  • Fixed null access for contractInfo in token processing
  • Used formatUnits from ethers for ERC-20 balance formatting
  • Improved handling of tokens array presence
  • +5/-5     
    TokenDropdown.tsx
    Fix ERC-20 token count and improve dropdown display           

    src/frontend/account/TokenDropdown/TokenDropdown.tsx

  • Corrected ERC-20 token count display to use actual token array length
  • Improved fallback for token name display in dropdown
  • Ensured robust access to contractInfo fields
  • +7/-2     
    account.ts
    Fix null access in token query by address                               

    src/storage/account.ts

  • Fixed possible null access for contractInfo and contractType
  • Improved robustness of token query by address
  • +2/-2     
    Enhancement
    Token.tsx
    Standardize token value formatting and null safety             

    src/frontend/token/Token.tsx

  • Replaced ethers utils.formatUnits with formatUnits for value
    formatting
  • Standardized value formatting for balances and total supply
  • Improved null safety for contract info fields
  • +5/-9     
    Ovewview.tsx
    Enhance ERC-20 transfer display in transaction overview   

    src/frontend/transaction/TransactionDetail/Overview/Ovewview.tsx

  • Added logic to detect and display ERC-20 transfer transactions
  • Enhanced "To" and "Value" fields for ERC-20 transfers
  • Improved fallback for contract info fields in transaction details
  • +41/-5   
    calculateValue.ts
    Standardize ERC-20 value calculation and formatting           

    src/frontend/utils/calculateValue.ts

  • Replaced ethers utils.formatUnits with formatUnits
  • Improved null safety for decimals in token value calculation
  • Standardized value formatting logic for ERC-20 tokens
  • +4/-4     

    Need help?
  • Type /help how to ... in the comments thread for any questions about PR-Agent usage.
  • Check out the documentation for more information.
  • @github-actions
    Copy link
    Contributor

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    ⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
    🏅 Score: 87
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Possible Null Handling Issue

    The code now checks for tokens && tokens.length > 0 before iterating, but the type of tokens and its initialization should be validated to ensure there are no runtime errors if tokens is undefined or not an array.

    if (tokens && tokens.length > 0) {
      tokens.forEach(
        (item: { contractType: ContractType; contractInfo: { decimals: string } | null; balance: BigNumberish }) => {
          if (item.contractType === ContractType.ERC_20) {
            const decimalsValue = item.contractInfo?.decimals ? parseInt(item.contractInfo.decimals) : 18
            item.balance = formatUnits(item.balance, decimalsValue)
          }
        }
      )
    }
    ERC-20 Transfer Logic

    The logic for detecting and displaying ERC-20 transfers is more complex and now conditionally renders based on token events. Ensure this logic works for all token event types and does not break for edge cases (e.g., multiple token transfers, missing contract info).

    const isERC20Transfer = transaction?.tokenTxs && 
      transaction.tokenTxs.length > 0 && 
      transaction.tokenTxs[0].tokenType === TokenType.ERC_20 &&
      (transaction.tokenTxs[0].tokenEvent === 'Transfer' || 
       transaction.tokenTxs[0].tokenEvent === 'Mint' || 
       transaction.tokenTxs[0].tokenEvent === 'Burn')
    
    const primaryERC20Transfer = isERC20Transfer ? transaction.tokenTxs[0] : null
    const renderErc20Tokens = (): JSX.Element | undefined => {
      const items = transaction?.tokenTxs
    
      if (
        items &&
        items.length > 0 &&
        (items[0].tokenType === TokenType.EVM_Internal || items[0].tokenType === TokenType.ERC_20)
      ) {
        // Determine the presence of different event types
        const hasApproval = items.some((item) => item.tokenEvent === 'Approval')
        const hasTransfer = items.some((item) => item.tokenEvent === 'Transfer')
    
        // Determine the title text based on the event types present
        let titleText = ''
        if (hasApproval && hasTransfer) {
          titleText = 'ERC-20 Tokens Approved and Transferred:'
        } else if (hasApproval) {
          titleText = 'ERC-20 Tokens Approved:'
        } else if (hasTransfer) {
          titleText = 'ERC-20 Tokens Transferred:'
        } else {
          titleText = 'Miscellaneous ERC-20 Token Events:'
        }
        return (
          <div className={styles.item}>
            <div className={styles.title}>{titleText}</div>
            <div className={styles.value}>
              <div className={styles.card}>
                {items.map((item, index) => (
                  <div key={index} className={styles.row}>
                    <Icon name="right_arrow" color="black" size="small" />
                    <span>From</span>
                    <Link href={`/account/${item.tokenFrom}`} className={styles.anchor}>
                      {item.tokenFrom}
                    </Link>
                    <span>To</span>
                    <Link href={`/account/${item.tokenTo}`} className={styles.anchor}>
                      {item.tokenTo}
                    </Link>
                    <span>For</span>
                    <div>{calculateTokenValue(item, item.tokenType, undefined, true)}&nbsp;</div>
                    <Link href={`/account/${item.contractAddress}`} className={styles.anchor}>
                      {item.tokenType === TokenType.EVM_Internal ? 'SHM' : item.contractInfo?.name || item.contractAddress}
                    </Link>
                  </div>
                ))}
              </div>
            </div>
          </div>
        )
      }
    }
    
    const renderErc721Tokens = (): JSX.Element | undefined => {
      const items = transaction?.tokenTxs
    
      if (items && items.length > 0 && items[0].tokenType === TokenType.ERC_721) {
        return (
          <div className={styles.item}>
            <div className={styles.title}>ERC-721 Tokens Transferred :</div>
            <div className={styles.value}>
              <div className={styles.card}>
                {items.map((item, index) => (
                  <Item
                    key={index}
                    from={item.tokenFrom}
                    to={item.tokenTo}
                    tokenId={calculateTokenValue(item, item?.tokenType)}
                    token={item?.contractInfo?.name || item?.contractAddress}
                    type="ERC-721"
                    contractAddress={item?.contractAddress}
                  />
                ))}
              </div>
            </div>
          </div>
        )
      }
    }
    
    const renderErc1155Tokens = (): JSX.Element | undefined => {
      const items = transaction?.tokenTxs
    
      if (items && items.length > 0 && items[0].tokenType === TokenType.ERC_1155) {
        return (
          <div className={styles.item}>
            <div className={styles.title}>ERC-1155 Tokens Transferred :</div>
            <div className={styles.value}>
              <div className={styles.card}>
                {items.map((item, index) => (
                  <Item
                    key={index}
                    from={item.tokenFrom}
                    to={item.tokenTo}
                    tokenId={calculateTokenValue(item, item?.tokenType, true)}
                    tokenValue={calculateTokenValue(item, item?.tokenType)}
                    token={item?.contractInfo?.name || item?.contractAddress}
                    type="ERC-1155"
                    contractAddress={item?.contractAddress}
                  />
                ))}
              </div>
            </div>
          </div>
        )
      }
    }
    
    if (transaction) {
      if ('txStatus' in transaction && transaction.txStatus) {
        return (
          <div className={styles.Ovewview}>
            <div className={styles.item}>
              <div className={styles.title}>Transaction Hash:</div>
              <div className={styles.value}>{transaction?.txHash}</div>
            </div>
            <div className={styles.item}>
              <div className={styles.title}>Transaction Status:</div>
              <div className={styles.value}>
                <Chip
                  title={
                    transaction?.txStatus === 'Pending'
                      ? 'Pending ............. ( Please wait for a bit.)'
                      : 'Expired ............. ( Please submit the transaction again.)'
                  }
                  size="medium"
                  color={transaction?.txStatus === 'Pending' ? 'gray' : 'error'}
                  className={styles.chip}
                />
              </div>
            </div>
            <div className={styles.item}>
              <div className={styles.title}>Method:</div>
              <div className={styles.value}>
                <Chip title={showTxMethod(transaction)} color="info" className={styles.chip} />
              </div>
            </div>
            <div className={styles.item}>
              <div className={styles.title}>Cycle:</div>
              <div className={styles.value}>{transaction?.cycle}</div>
            </div>
            <div className={styles.item}>
              <div className={styles.title}>Timestamp:</div>
              <div className={styles.value}>{transaction?.timestamp}</div>
            </div>
            {transaction.originalTxData?.readableReceipt && (
              <>
                <div className={styles.item}>
                  <div className={styles.title}>Nonce:</div>
                  <div className={styles.value}>
                    {transaction?.originalTxData?.readableReceipt?.nonce &&
                      web3.utils.hexToNumber(transaction?.originalTxData?.readableReceipt?.nonce)}
                  </div>
                </div>
    
                <div className={styles.item}>
                  <div className={styles.title}>From:</div>
                  <div className={styles.value}>
                    <Link href={`/account/${transaction?.originalTxData?.readableReceipt?.from}`} className={styles.link}>
                      {transaction?.originalTxData?.readableReceipt?.from}
                    </Link>
                  </div>
                </div>
    
                <div className={styles.item}>
                  <div className={styles.title}>To:</div>
                  <div className={styles.value}>
                    {transaction?.originalTxData?.readableReceipt?.to ? (
                      <Link href={`/account/${transaction?.originalTxData?.readableReceipt?.to}`} className={styles.link}>
                        {transaction?.originalTxData?.readableReceipt?.to}
                      </Link>
                    ) : (
                      'Contract Creation'
                    )}
                  </div>
                </div>
    
                <div className={styles.item}>
                  <div className={styles.title}>Value:</div>
                  <div className={styles.value}>
                    {calculateFullValue(`${transaction?.originalTxData?.readableReceipt?.value}`)} SHM
                  </div>
                </div>
                {transaction?.originalTxData?.readableReceipt?.internalTxData && (
                  <>
                    <div className={styles.item}>
                      <div className={styles.title}>Node Address:</div>
                      <div className={styles.value}>
                        <Link
                          href={`/account/${transaction?.originalTxData?.readableReceipt?.internalTxData?.nominee}`}
                          className={styles.link}
                        >
                          {transaction?.originalTxData?.readableReceipt?.internalTxData?.nominee}
                        </Link>
                      </div>
                    </div>
                    {transaction?.transactionType === TransactionType.StakeReceipt && (
                      <div className={styles.item}>
                        <div className={styles.title}>Stake Amount:</div>
                        <div className={styles.value}>
                          {calculateFullValue(
                            `0x${Number(transaction?.originalTxData?.readableReceipt?.internalTxData?.stake).toString(
                              16
                            )}`
                          )}{' '}
                          SHM
                        </div>
                      </div>
                    )}
                  </>
                )}
              </>
            )}
          </div>
        )
      } else {
        return (
          <div className={styles.Ovewview}>
            <div className={styles.item}>
              <div className={styles.title}>Transaction Hash:</div>
              <div className={styles.value}>{transaction?.txHash}</div>
            </div>
    
            <div className={styles.item}>
              <div className={styles.title}>Status:</div>
              <div className={styles.value}>
                <Chip
                  title={transaction?.wrappedEVMAccount?.readableReceipt?.status === 1 ? 'success' : 'failed'}
                  color={transaction?.wrappedEVMAccount?.readableReceipt?.status === 1 ? 'success' : 'error'}
                  className={styles.chip}
                />
              </div>
            </div>
    
            <div className={styles.item}>
              <div className={styles.title}>Type:</div>
              <div className={styles.value}>
                <Chip title={showTxMethod(transaction)} color="info" className={styles.chip} />
              </div>
            </div>
    
            <div className={styles.item}>
              <div className={styles.title}>Cycle:</div>
              <div className={styles.value}>{transaction?.cycle}</div>
            </div>
    
            <div className={styles.item}>
              <div className={styles.title}>Timestamp:</div>
              <div className={styles.value}>
                {moment(transaction?.timestamp).fromNow()} ({toReadableDateFromMillis(transaction?.timestamp)})
              </div>
            </div>
    
            {!isInternalTx && (
              <div className={styles.item}>
                <div className={styles.title}>Nonce:</div>
                <div className={styles.value}>
                  {transaction?.wrappedEVMAccount?.readableReceipt?.nonce &&
                    web3.utils.hexToNumber(transaction?.wrappedEVMAccount?.readableReceipt?.nonce)}
                </div>
              </div>
            )}
    
            <div className={styles.item}>
              <div className={styles.title}>From:</div>
              <div className={styles.value}>
                <Link href={`/account/${transaction?.txFrom}`} className={styles.link}>
                  {transaction?.txFrom}
                </Link>
              </div>
            </div>
    
            <div className={styles.item}>
              <div className={styles.title}>To:</div>
              <div className={styles.value}>
                {transaction?.wrappedEVMAccount?.readableReceipt?.to ? (
                  isERC20Transfer && primaryERC20Transfer ? (
                    <div>
                      <Link href={`/account/${primaryERC20Transfer.tokenTo}`} className={styles.link}>
                        {primaryERC20Transfer.tokenTo}
                      </Link>
                      <div style={{ fontSize: '0.8em', color: '#666', marginTop: '4px' }}>
                        via{' '}
                        <Link href={`/account/${transaction?.txTo}`} className={styles.link}>
                          {primaryERC20Transfer.contractInfo?.name || 'Token Contract'}
                        </Link>
                      </div>
                    </div>
                  ) : (
                    <Link href={`/account/${transaction?.txTo}`} className={styles.link}>
                      {transaction?.txTo}
                    </Link>
                  )
                ) : (
                  <div>
                    <Link
                      href={`/account/${transaction?.wrappedEVMAccount?.readableReceipt?.contractAddress}`}
                      className={styles.link}
                    >
                      {transaction?.wrappedEVMAccount?.readableReceipt?.contractAddress}
                    </Link>
                    {transaction?.wrappedEVMAccount?.readableReceipt?.status === 1 && ' (Contract created)'}
                  </div>
                )}
              </div>
            </div>
    
            {transaction?.nominee && (
              <>
                <div className={styles.item}>
                  <div className={styles.title}>Node Address:</div>
                  <div className={styles.value}>
                    <Link href={`/account/${transaction?.nominee}`} className={styles.link}>
                      {transaction?.nominee}
                    </Link>
                  </div>
                </div>
                {transaction?.transactionType === TransactionType.StakeReceipt ? (
                  <div className={styles.item}>
                    <div className={styles.title}>Stake Amount:</div>
                    <div className={styles.value}>
                      {calculateFullValue(`0x${transaction?.wrappedEVMAccount?.readableReceipt?.stakeInfo?.stake}`)} SHM
                    </div>
                  </div>
                ) : (
                  <>
                    <div className={styles.item}>
                      <div className={styles.title}>Reward:</div>
                      <div className={styles.value}>
                        {calculateFullValue(`0x${transaction?.wrappedEVMAccount?.readableReceipt?.stakeInfo?.reward}`)}{' '}
                        SHM
                      </div>
                    </div>
                    <div className={styles.item}>
                      <div className={styles.title}>Stake Amount:</div>
                      <div className={styles.value}>
                        {calculateFullValue(`0x${transaction?.wrappedEVMAccount?.readableReceipt?.stakeInfo?.stake}`)} SHM
                      </div>
                    </div>
                    <div className={styles.item}>
                      <div className={styles.title}>Unstake Amount:</div>
                      <div className={styles.value}>
                        {calculateFullValue(
                          `0x${transaction?.wrappedEVMAccount?.readableReceipt?.stakeInfo?.totalUnstakeAmount}`
                        )}{' '}
                        SHM
                      </div>
                    </div>
                  </>
                )}
              </>
            )}
    
            {internalTxType === InternalTXType.TransferFromSecureAccount && (
              <div className={styles.item}>
                <div className={styles.title}>Value:</div>
                <div className={styles.value}>
                  {calculateFullValue(`${transaction?.wrappedEVMAccount?.amountSpent ?? 0}`)} SHM
                </div>
              </div>
            )}
    
            {!isInternalTx && (
              <div className={styles.item}>
                <div className={styles.title}>Value:</div>
                <div className={styles.value}>
                  {isERC20Transfer && primaryERC20Transfer ? (
                    <div>
                      {calculateTokenValue(primaryERC20Transfer, primaryERC20Transfer.tokenType, undefined, true)}{' '}
                      {primaryERC20Transfer.contractInfo?.symbol || primaryERC20Transfer.contractAddress}
                      {transaction?.wrappedEVMAccount?.readableReceipt?.value !== '0x0' && (
                        <div style={{ fontSize: '0.8em', color: '#666', marginTop: '4px' }}>
                          + {calculateFullValue(`${transaction?.wrappedEVMAccount?.readableReceipt?.value ?? 0}`)} SHM
                        </div>
                      )}
                    </div>
                  ) : (
                    `${calculateFullValue(`${transaction?.wrappedEVMAccount?.readableReceipt?.value ?? 0}`)} SHM`
                  )}
                </div>
    Data Consistency

    The addition of contractInfo and contractType as possibly null values in the returned object may affect downstream consumers. Ensure all usages of these fields handle null values gracefully.

      contractInfo: accountExist?.contractInfo || null,
      contractType: accountExist?.contractType || null,
      balance: tokenValue,
    })

    Comment on lines 88 to 92
    (item: { contractType: ContractType; contractInfo: { decimals: string } | null; balance: BigNumberish }) => {
    if (item.contractType === ContractType.ERC_20) {
    const decimalsValue = item.contractInfo.decimals ? parseInt(item.contractInfo.decimals) : 18
    item.balance = utils.formatUnits(item.balance, decimalsValue)
    const decimalsValue = item.contractInfo?.decimals ? parseInt(item.contractInfo.decimals) : 18
    item.balance = formatUnits(item.balance, decimalsValue)
    }
    Copy link
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Suggestion: Modifying item.balance directly mutates the original token object, which can cause unintended side effects if the tokens array is reused elsewhere. Instead, create a new array with updated balances and avoid mutating the original objects. [general, importance: 7]

    Suggested change
    (item: { contractType: ContractType; contractInfo: { decimals: string } | null; balance: BigNumberish }) => {
    if (item.contractType === ContractType.ERC_20) {
    const decimalsValue = item.contractInfo.decimals ? parseInt(item.contractInfo.decimals) : 18
    item.balance = utils.formatUnits(item.balance, decimalsValue)
    const decimalsValue = item.contractInfo?.decimals ? parseInt(item.contractInfo.decimals) : 18
    item.balance = formatUnits(item.balance, decimalsValue)
    }
    tokens = tokens.map(
    (item: { contractType: ContractType; contractInfo: { decimals: string } | null; balance: BigNumberish }) => {
    if (item.contractType === ContractType.ERC_20) {
    const decimalsValue = item.contractInfo?.decimals ? parseInt(item.contractInfo.decimals) : 18
    return { ...item, balance: formatUnits(item.balance, decimalsValue) }
    }
    return item
    }
    )

    Comment on lines +403 to +414
    <div>
    {calculateTokenValue(primaryERC20Transfer, primaryERC20Transfer.tokenType, undefined, true)}{' '}
    {primaryERC20Transfer.contractInfo?.symbol || primaryERC20Transfer.contractAddress}
    {transaction?.wrappedEVMAccount?.readableReceipt?.value !== '0x0' && (
    <div style={{ fontSize: '0.8em', color: '#666', marginTop: '4px' }}>
    + {calculateFullValue(`${transaction?.wrappedEVMAccount?.readableReceipt?.value ?? 0}`)} SHM
    </div>
    )}
    </div>
    ) : (
    `${calculateFullValue(`${transaction?.wrappedEVMAccount?.readableReceipt?.value ?? 0}`)} SHM`
    )}
    Copy link
    Contributor

    Choose a reason for hiding this comment

    The reason will be displayed to describe this comment to others. Learn more.

    Suggestion: The check for transaction?.wrappedEVMAccount?.readableReceipt?.value !== '0x0' may fail if the value is undefined or in a different format (e.g., '0'). Use a more robust check to ensure correct display of the SHM value. [general, importance: 6]

    Suggested change
    <div>
    {calculateTokenValue(primaryERC20Transfer, primaryERC20Transfer.tokenType, undefined, true)}{' '}
    {primaryERC20Transfer.contractInfo?.symbol || primaryERC20Transfer.contractAddress}
    {transaction?.wrappedEVMAccount?.readableReceipt?.value !== '0x0' && (
    <div style={{ fontSize: '0.8em', color: '#666', marginTop: '4px' }}>
    + {calculateFullValue(`${transaction?.wrappedEVMAccount?.readableReceipt?.value ?? 0}`)} SHM
    </div>
    )}
    </div>
    ) : (
    `${calculateFullValue(`${transaction?.wrappedEVMAccount?.readableReceipt?.value ?? 0}`)} SHM`
    )}
    {isERC20Transfer && primaryERC20Transfer ? (
    <div>
    {calculateTokenValue(primaryERC20Transfer, primaryERC20Transfer.tokenType, undefined, true)}{' '}
    {primaryERC20Transfer.contractInfo?.symbol || primaryERC20Transfer.contractAddress}
    {transaction?.wrappedEVMAccount?.readableReceipt?.value && transaction?.wrappedEVMAccount?.readableReceipt?.value !== '0x0' && transaction?.wrappedEVMAccount?.readableReceipt?.value !== '0' && (
    <div style={{ fontSize: '0.8em', color: '#666', marginTop: '4px' }}>
    + {calculateFullValue(`${transaction?.wrappedEVMAccount?.readableReceipt?.value ?? 0}`)} SHM
    </div>
    )}
    </div>
    ) : (
    `${calculateFullValue(`${transaction?.wrappedEVMAccount?.readableReceipt?.value ?? 0}`)} SHM`
    )}

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Projects

    None yet

    Development

    Successfully merging this pull request may close these issues.

    3 participants